<?php

namespace api\modules\app\services;

use api\components\UserCenter;
use api\modules\app\models\Event;
use api\modules\app\models\EventFilterOuter;
use api\modules\app\models\EventStatus;
use api\modules\manage\models\DictElements;
use api\modules\manage\models\MenusLinks;
use api\modules\manage\resources\ElementResource;
use api\modules\manage\services\MenuService;
use Exception;
use PHPUnit\DbUnit\Database\DataSet;
use Yii;
use yii\elasticsearch\Query;
use yii\helpers\ArrayHelper;

class ElasticSearchService
{
    const DEFAULT_TRY_TIMES = 2;

    const DEFAULT_ES_INDEX = 'os_event_filter';
    const DEFAULT_ES_TYPE = '_doc'; // 不在支持 index 下多 type 所以使用 _doc
    const DEFAULT_ES_MAPPING_OPTION = ['include_type_name' => 'false'];

    const DEFAULT_PAGINATION = ['offset' => 0, 'limit' => 10];
    const DEFAULT_ORDERBY = ['event.event_time' => ['order' => 'desc']];

    public static function search($condition, $orderBy = self::DEFAULT_ORDERBY, $pagination = self::DEFAULT_PAGINATION, $menuId = null, $opter = [])
    {
        $query = new Query();
        $query->from(self::DEFAULT_ES_INDEX)
              ->query($condition)
              ->orderBy($orderBy)
              ->offset($pagination['offset'])
              ->limit($pagination['limit']);

        $tryTimes = self::DEFAULT_TRY_TIMES;
        do {
            try {
                $response = $query->createCommand()->search();
                $success = true;
            } catch (\Exception $e) {
                $tryTimes--;
                $success = false;
            }
        } while (!$success && $tryTimes > 0);
        if (!$success) {
            throw $e;
        }

        return self::formatReaponse($response, $menuId, '', $opter);
    }

    public static function addMappingFields($fields)
    {
        foreach ($fields as $key => $type) {
            $mapping['properties'][$key] = ['type' => $type];
        }
        return (new Query)->from(self::DEFAULT_ES_INDEX)
                    ->createCommand()
                    ->setMapping(self::DEFAULT_ES_INDEX, null, $mapping, self::DEFAULT_ES_MAPPING_OPTION);
    }

    public static function updateMapping()
    {
        $mapping = [
            'properties' => [
                Event::tableName() => [
                    'type' => 'object',
                    'properties' => self::covertDbProperties(Event::class),
                ],
                EventFilterOuter::tableName() => [
                    'type' => 'object',
                    'properties' => self::covertDbProperties(EventFilterOuter::class),
                ],
                EventStatus::tableName() => [
                    'type' => 'object',
                    'properties' => self::covertDbProperties(EventStatus::class),
                ],
            ],
        ];

        return (new Query)->from(self::DEFAULT_ES_INDEX)
                    ->createCommand()
                    ->setMapping(self::DEFAULT_ES_INDEX, null, $mapping, self::DEFAULT_ES_MAPPING_OPTION);
    }

    public static function getMapping()
    {
        return (new Query)->from(self::DEFAULT_ES_INDEX)
                                        ->createCommand()
                                        ->getMapping(self::DEFAULT_ES_INDEX, null, self::DEFAULT_ES_MAPPING_OPTION);
    }

    public static function create()
    {
        $index = self::DEFAULT_ES_INDEX;
        $command = (new Query())->createCommand(Yii::$app->elasticsearch);
        $command->deleteIndex($index);
        $config = [
            'settings' => ['number_of_replicas' => 1,],
            'mappings' => [
                'properties' => [
                    Event::tableName() => [
                        'type' => 'object',
                        'properties' => self::covertDbProperties(Event::class),
                    ],
                    EventFilterOuter::tableName() => [
                        'type' => 'object',
                        'properties' => self::covertDbProperties(EventFilterOuter::class),
                    ],
                    EventStatus::tableName() => [
                        'type' => 'object',
                        'properties' => self::covertDbProperties(EventStatus::class),
                    ],
                ],
            ]
        ];
        return $command->createIndex($index, $config, self::DEFAULT_ES_MAPPING_OPTION);
    }

    private static function covertDbProperties($class)
    {
        $properties = [];
        $schema = $class::getTableSchema();
        foreach ($schema->columns as $key => $config) {
            $type = $config->type;
            switch ($config->type) {
                case 'datetime':
                case 'timestamp':
                    $type = 'date';
                    break;
                case 'smallint':
                    $type = 'short';
                    break;
                case 'bigint':
                    $type = 'long';
                    break;
                case 'string':
                    $type = 'keyword';
                    break;
                default:
                    $type = $config->type;
                    break;
            }
            if (strpos($key, 'address')) {
                $type = 'text';
            }
            $properties[$key] = [
                'type' => $type,
            ];
        }
        return $properties;
    }

    public static function formatReaponse($response, $menuId = null, $token = '', $opter = [])
    {
        $hits = $response['hits']['hits'] ?? [];
        $data = [];
        if (empty($token)) {
            $headers = Yii::$app->request->headers;
            $token = $headers['authorization'];
        }
        $menuCode = MenuService::getCodeById($menuId);
        $userActions = UserCenter::getUserActions($token, true, $menuCode);

        foreach ($hits as $hit) {
            $item = $hit['_source'];
            $item = (new EventService())->formatListItem($item, $userActions, $opter);
            $data[] = $item;
        }

        return [
            'data' => $data,
            'count' => $response['hits']['total'],
        ];
    }

    public static function update($id, $data)
    {
        $command = Yii::$app->elasticsearch->createCommand();
        $cached = [];
        $data = self::formatEsData($data, $cached, false);

        $tryTimes = self::DEFAULT_TRY_TIMES;
        do {
            try {
                $response = $command->update(self::DEFAULT_ES_INDEX, self::DEFAULT_ES_TYPE, $id, $data, ['refresh' => 'true']);
                $success = true;
            } catch (\Exception $e) {
                $tryTimes--;
                $success = false;
            }
        } while (!$success && $tryTimes);
        if (!$success) {
            throw $e;
        }

        return $response;
    }

    /**
     * 根据 eventId 和 coopId 批量更新 es
     */
    public static function upsert($eventId, $params = [])
    {
        $condition = [
            EventFilterOuter::tableName() . '.event_id' => $eventId,
        ];
        if (isset($params['coopId'])) {
            $condition[EventFilterOuter::tableName() . 'coop_id'] = $params['coopId'];
        }
        $query = EventService::getCommonJoinQuery();
        $query->select(EventService::getListColumns())
              ->andFilterWhere($condition)
              ->asArray();
        $bulk = Yii::$app->elasticsearch->createBulkCommand(['options' => ['refresh' => 'true']]);
        $line1 = [
            'index' => ['_index' => self::DEFAULT_ES_INDEX, '_type' => '_doc'],
        ];
        $cached = self::getTableProperties();
        foreach ($query->each() as $item) {
            $line2 = self::formatEsData($item, $cached);
            $line1['index']['_id'] = $line2['event_filter_outer']['id'];
            $bulk->addAction($line1, $line2);
        }
        $response = $bulk->execute();
        return self::handleResponse($response);
    }

    private static function handleResponse($response)
    {
        if (!$response['errors']) {
            return true;
        }
        foreach ($response['items'] as $item) {
            $errors[] = $item['index']['error'];
        }
        throw new Exception(json_encode($errors));
    }

    public static function batchSync($conditions = [])
    {
        $bulk = Yii::$app->elasticsearch->createBulkCommand();
        $query = EventService::getCommonJoinQuery();
        $query->select(EventService::getListColumns())->asArray();
        if (!empty($conditions)) {
            foreach ($conditions as $condition) {
                $query->andFilterWhere($condition);
            }
        }
        $line1 = [
            'index' => ['_index' => self::DEFAULT_ES_INDEX, '_type' => '_doc'],
        ];
        $cached = self::getTableProperties();
        foreach ($query->each() as $item) {
            $line2 = self::formatEsData($item, $cached);
            $line1['index']['_id'] = $line2['event_filter_outer']['id'];
            $bulk->addAction($line1, $line2);
        }
        $response = $bulk->execute();
        return self::handleResponse($response);
    }

    /**
     * @param array item：数据
     * @param array cached: 表字段类型
     * @param bool isSqlData: 原始 query 数据，false: 格式化过的数据
     */
    private static function formatEsData($item, &$cached, $isSqlData = true)
    {
        $line2 = $item;
        if ($isSqlData) {
            $line2 = EventService::convertDbQueryColumn($item);
        }
        $formated = [];
        foreach ($line2 as $table => $data) {
            if ($table == Event::tableName()) {
                $class = Event::class;
            } elseif ($table == EventFilterOuter::tableName()) {
                $class = EventFilterOuter::class;
            } else {
                $class = EventStatus::class;
            }
            if (empty($cached[$table])) {
                $cached[$table] = self::covertDbProperties($class);
            }
            $properties = $cached[$table];
            $item = [];
            foreach ($data as $key => $value) {
                if (!isset($value)) {
                    continue;
                }
                if ($properties[$key]['type'] == 'date') {
                    $value = strtotime($value) ?? time();
                }
                $item[$key] = $value;
            }
            $formated[$table] = $item;
        }
        return $formated;
    }

    /**
     * 缓存 mysql 表结构，方便同步到 es
     * TODO: 直接写死在 model 中不用每次都查
     */
    public static function getTableProperties()
    {
        $key = 'paidan_event_table_schema';
        $redis = Yii::$app->redisCache;
        $cached = $redis->get($key);
        if (!empty($cached)) {
            return json_decode($cached, true);
        }
        $cached = [];
        $needCached = [
            'event' => Event::class,
            'event_filter_outer' => EventFilterOuter::class,
            'event_status' => EventStatus::class,
        ];
        foreach ($needCached as $table => $class) {
            $cached[$table] = self::covertDbProperties($class);
        }
        $redis->set($key, json_encode($cached));

        return $cached;
    }
}
